package org.taobao.ibatiside.ui.util;

import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.datatools.modelbase.sql.query.Predicate;
import org.eclipse.datatools.modelbase.sql.query.PredicateBasic;
import org.eclipse.datatools.modelbase.sql.query.PredicateIn;
import org.eclipse.datatools.modelbase.sql.query.PredicateInValueList;
import org.eclipse.datatools.modelbase.sql.query.PredicateInValueSelect;
import org.eclipse.datatools.modelbase.sql.query.PredicateIsNull;
import org.eclipse.datatools.modelbase.sql.query.PredicateLike;
import org.eclipse.datatools.modelbase.sql.query.QueryDeleteStatement;
import org.eclipse.datatools.modelbase.sql.query.QueryInsertStatement;
import org.eclipse.datatools.modelbase.sql.query.QuerySearchCondition;
import org.eclipse.datatools.modelbase.sql.query.QuerySelect;
import org.eclipse.datatools.modelbase.sql.query.QuerySelectStatement;
import org.eclipse.datatools.modelbase.sql.query.QueryStatement;
import org.eclipse.datatools.modelbase.sql.query.QueryUpdateStatement;
import org.eclipse.datatools.modelbase.sql.query.SearchConditionCombined;
import org.eclipse.datatools.modelbase.sql.query.TableExpression;
import org.eclipse.datatools.modelbase.sql.query.TableInDatabase;
import org.eclipse.datatools.modelbase.sql.query.TableReference;
import org.eclipse.datatools.modelbase.sql.query.helper.StatementHelper;
import org.eclipse.datatools.sqltools.editor.core.connection.ISQLEditorConnectionInfo;
import org.eclipse.datatools.sqltools.parsers.sql.SQLParserException;
import org.eclipse.datatools.sqltools.parsers.sql.SQLParserInternalException;
import org.eclipse.datatools.sqltools.sqlbuilder.model.OmitSchemaInfo;
import org.eclipse.datatools.sqltools.sqlbuilder.model.SQLDomainModel;
import org.eclipse.datatools.sqltools.sqleditor.SQLEditorConnectionInfo;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMap.Entry;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.swt.widgets.Shell;
import org.taobao.ibatiside.core.filemodel.FileProber;
import org.taobao.ibatiside.core.filemodel.IbatisConfigFile;
import org.taobao.ibatiside.core.filemodel.IbatisProject;
import org.taobao.ibatiside.model.mapperv2.GeneralStatement;
import org.taobao.ibatiside.model.mapperv2.Mapperv2Factory;
import org.taobao.ibatiside.model.mapperv2.ParameterMap;
import org.taobao.ibatiside.model.mapperv2.ResultMap;
import org.taobao.ibatiside.model.mapperv2.SqlMap;
import org.taobao.ibatiside.model.mapperv2.TypeAlias;
import org.taobao.ibatiside.parser.SqlMapParser;
import org.taobao.ibatiside.ui.wizard.BatisSettingModel;
import org.taobao.ibatiside.ui.wizard.DataObjectField;


public final class MiscHelper {
	
	public static final Pattern DAO_API_PATTERN=Pattern.compile("dao-interface-([\\w_-]+)\\.javajet");
	public static final Pattern DAO_IMPL_PATTERN=Pattern.compile("dao-impl-([\\w_-]+)\\.javajet");
	public static final Pattern DATAOBJECT_PATTERN=Pattern.compile("dataobject-([\\w_-]+)\\.javajet");
	public static final Pattern SPRING_PATTERN=Pattern.compile("spring-([\\w_-]+)\\.xmljet");
	public static final String DEFAULT_TEMPLATES_LOC_PATH="resource/batis-template/v2";
	public static final String[] SQLMAP_INTERNAL_TYPE={
		"string",
		"byte",
		"long",
		"short",
		"int",
		"integer",
		"double",
		"float",
		"boolean",
		"object",
		"decimal",
		"date",
		"map",
		"hashmap",
		"list",
		"arraylist",
		"collection",
		"iterator",
		"cursor"
	};
	public static final String[] JDBC_TYPE = {
		"BIT","TINYINT","SMALLINT","INTEGER","BIGINT","FLOAT","REAL","DOUBLE","NUMERIC","DECIMAL","CHAR","VARCHAR","LONGVARCHAR","DATE","TIME","TIMESTAMP","BINARY","VARBINARY","LONGVARBINARY","NULL","OTHER","JAVA_OBJECT","DISTINCT","STRUCT","ARRAY","BLOB","CLOB","REF","DATALINK","BOOLEAN","ROWID","NCHAR","NVARCHAR","LONGNVARCHAR","NCLOB","SQLXML"
	};
	public static final TypeWrapper[] INTERAL_TYPE_WRAPPER={
		new TypeWrapper("string",1),
		new TypeWrapper("byte",1),
		new TypeWrapper("long",1),
		new TypeWrapper("short",1),
		new TypeWrapper("int",1),
		new TypeWrapper("double",1),
		new TypeWrapper("float",1),
		new TypeWrapper("boolean",1),
		new TypeWrapper("decimal",1),
		new TypeWrapper("date",1),
	};
	public static final TypeWrapper TYPEWRAPPER_INTERAL_CATEGORY=new TypeWrapper("--Interal--", TypeWrapper.SORT);
	public static final TypeWrapper TYPEWRAPPER_ALIAS_CATEGORY=new TypeWrapper("--Alias--", TypeWrapper.SORT);
	public static final TypeWrapper TYPEWRAPPER_DO_CATEGORY=new TypeWrapper("Data Object", TypeWrapper.NEW_CREATE);
	public static final TypeWrapper TYPEWRAPPER_SELECT_CATEGORY = new TypeWrapper("--Select--", TypeWrapper.SELECT);
	public static final String[] MAPPING_JAVA_TYPE={
		"int",
		"Integer",
		"String",
		Date.class.getName(),
		Time.class.getName(),
		Timestamp.class.getName(),
		"boolean",
		"Boolean",
		"long",
		"Long",
		"double",
		"Double",
		"char",
		"Char",
		"float",
		"Float",
		"byte[]",
		BigDecimal.class.getName(),
		"Object"
	};
	public static String getRawFn(String fileName, Pattern pattern){
		if (StringUtils.isNotBlank(fileName)){
			Matcher matcher = pattern.matcher(fileName);
			if (matcher.matches()){
				return matcher.group(1);
			}
		}
		return null;
	}
	public static String getDaoApiFn(String rawName){
		return "dao-interface-"+rawName+".javajet";
	}
	
	public static String getDaoImplFn(String rawName){
		return "dao-impl-"+rawName+".javajet";
	}
	
	public static String getDataObjectFn(String rawName){
		return "dataobject-"+rawName+".javajet";
	}
	public static final AtomicInteger RENAME_SEED = new AtomicInteger(0);
	public static String sqlId2JavaId(String sqlName){
		if (StringUtils.isBlank(sqlName)) return "";
		StringBuffer buffer =  new StringBuffer();
		int upper_tip = -100;
		for (int i =0; i < sqlName.length() ; i++){
			char c = sqlName.charAt(i);
			if (c == '#'){
				continue;
			}else if (c == '$' || c == ' ' || c == '_'){
				upper_tip = i;
				continue;
			}else{
				if (upper_tip == i-1){
					c= Character.toUpperCase(c);
				}else{
					c = Character.toLowerCase(c);
				}
				buffer.append(c);
			}
		}
		if (CodeGenUtil.isJavaReservedWord(buffer.toString())){
			buffer.append(RENAME_SEED.getAndIncrement());
		}
		return buffer.toString();
	}
	//produce a constant-like name instead of a formal variable name,
		//for example: 'abcEfg' -> 'ABC_EFG'
	public static String getConstName(CharSequence variableName){
		StringBuffer sb = new StringBuffer();
		int last_uppered = -1;
		for (int i = 0; i < variableName.length() ; i++){
			char c = variableName.charAt(i);
			boolean isup = Character.isUpperCase(c);
			if (i > last_uppered+1 && isup){
				sb.append('_');
			}
			if (isup){
				last_uppered = i;
			}
			
			sb.append(Character.toUpperCase(c));
		}
		return sb.toString();
	}
	public static IbatisConfigFile convert2IbatisConfig(IFile configFile){
		if (configFile != null && configFile.isAccessible()){
			IProject project = configFile.getProject();
			int pv = FileProber.getIbatisVerion(project);
			IbatisProject ip = new IbatisProject(project, pv);
			IbatisConfigFile config = new IbatisConfigFile(configFile, ip);
			return config;
		}
		return null;
	}
	public static QueryStatement parseQueryStatement(BatisSettingModel model){
		if (model.getDtpStmt() != null) return model.getDtpStmt();
		else{
			SQLDomainModel dm = new SQLDomainModel();
			OmitSchemaInfo info = new OmitSchemaInfo();
			info.initFromPreferences();
			dm.setOmitSchemaInfo(info);
			ISQLEditorConnectionInfo conn_info = new SQLEditorConnectionInfo(null, model.getConnectionProfile().getName(), "");
			dm.setConnectionInfo(conn_info);
			try {
				return dm.parse(model.getOpSql());
			} catch (SQLParserException e) {
				e.printStackTrace();
			} catch (SQLParserInternalException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	public static GeneralStatement createEMFStatementByDtpStatement(QueryStatement statement){
		if (statement instanceof QuerySelectStatement){
			return Mapperv2Factory.eINSTANCE.createSelect();
		}else if (statement instanceof QueryUpdateStatement){
			return Mapperv2Factory.eINSTANCE.createUpdate();
		}else if (statement instanceof QueryInsertStatement){
			return Mapperv2Factory.eINSTANCE.createInsert();
		}else if (statement instanceof QueryDeleteStatement){
			return Mapperv2Factory.eINSTANCE.createDelete();
		}
		return Mapperv2Factory.eINSTANCE.createStatement();
	}
	public static boolean needCreateNewEmfStatement(QueryStatement oldStmt,
			QueryStatement newStmt) {
		if (oldStmt == null) return true;
		if (newStmt == null) return false;
		return oldStmt.getClass() != newStmt.getClass();
	}
	public static String reduceStatementId(BatisSettingModel model) {
		INamingConversion nc = model.getNamingConversion();
		QueryStatement dtpStmt = model.getDtpStmt();
		List current_tables = StatementHelper.getTablesForStatement(dtpStmt);
		QuerySearchCondition current_conditions = StatementHelper.getSearchCondition(dtpStmt);
		List<String> loadConditions = new ArrayList<String>();
		extractCondition(loadConditions, current_conditions);
		List<String> tables = new ArrayList<String>(3);
		for(Iterator ite = current_tables.iterator(); ite.hasNext(); ){
			TableExpression te = (TableExpression) ite.next();
			if (te instanceof TableInDatabase){
				tables.add(((TableInDatabase)te).getName());
			}
		}
		String[] _tables = tables.toArray(new String[tables.size()]);
		String[] _condition = loadConditions.toArray(new String[loadConditions.size()]);
		if (dtpStmt instanceof QuerySelectStatement){
			return nc.getSelectStmtName(_tables, _condition);
		}else if (dtpStmt instanceof QueryUpdateStatement){
			return nc.getUpdateStmtName(_tables, _condition);
		}else{
			return "";
		}
	}
	private static void extractCondition(
			List<String> loadConditions, QuerySearchCondition condition) {
		if (condition == null) return;
		if (condition instanceof SearchConditionCombined){
			extractCondition(loadConditions, ((SearchConditionCombined)condition).getLeftCondition());
			extractCondition(loadConditions, ((SearchConditionCombined)condition).getRightCondition());
		}else if (condition instanceof Predicate){
			String condition_column =  null;
			if (condition instanceof PredicateBasic){
				condition_column = ((PredicateBasic)condition).getLeftValueExpr().getName();
			}else if (condition instanceof PredicateLike){
				condition_column = ((PredicateLike)condition).getPatternValueExpr().getName();
			}else if (condition instanceof PredicateInValueSelect){
				condition_column = ((PredicateInValueSelect)condition).getValueExpr().getName();
			}else if (condition instanceof PredicateIsNull){
				condition_column = ((PredicateIsNull)condition).getValueExpr().getName();
			}else{
				//more
			}
			if (StringUtils.isNotBlank(condition_column)){
				loadConditions.add(condition_column);
			}
		}
	}
	public static void disposeBinding(Binding binding) {
		if (binding != null && !binding.isDisposed()){
			binding.dispose();
		}
	}
	public static GeneralStatement getStatementById(SqlMap sqlmap, String id){
		FeatureMap gp = sqlmap.getGroup();
		for (Iterator<Entry> ite = gp.iterator(); ite.hasNext();){
			Object entry = ite.next().getValue();
			if (entry instanceof GeneralStatement){
				if (id.equalsIgnoreCase(((GeneralStatement)entry).getId())){
					return (GeneralStatement) entry;
				}
			}
		}
		return null;
	}
	public static ParameterMap getParameterMapById(SqlMap sqlmap, String id){
		for (ParameterMap map : sqlmap.getParameterMap()){
			if (id.equals(map.getId())){
				return map;
			}
		}
		return null;
	}
	public static List<ClassField> toBeClassField(List<DataObjectField> fields){
		List<ClassField> fs = new ArrayList<ClassField>();
		for (DataObjectField f : fields){
			ClassField cf = new ClassField(f.getFieldName());
			cf.setGetExisted(true);
			cf.setSetExisted(true);
			fs.add(cf);
		}
		return fs;
	}
	public static List<ClassField> loadJavaBeanFields(IType type) throws JavaModelException{
		List<ClassField> fields = new LinkedList<ClassField>();
		IMethod[] methods = type.getMethods();
		for(IMethod method : methods){
			int flags = method.getFlags();
			String mn = method.getElementName();
			if (Flags.isPublic(flags) && !Flags.isStatic(flags)){
				if (mn.startsWith("get") && method.getParameterTypes().length == 0){
					updateField(fields,mn.substring(3),method.getReturnType(),true);
				}else if (mn.startsWith("is") && method.getParameterTypes().length == 0){
					updateField(fields, mn.substring(2), method.getReturnType(), true);
				}else if (mn.startsWith("set") && method.getParameterTypes().length == 1){
					updateField(fields,mn.substring(3), method.getParameterTypes()[0], false);
				}
			}
		}
		return fields;
	}
	public static List<ClassField> loadWritableFields(IType type) throws JavaModelException{
		List<ClassField> all_fields = MiscHelper.loadJavaBeanFields(type);
		List<ClassField> writable_fields = new ArrayList<ClassField>(all_fields.size());
		for (ClassField one_field : all_fields){
			if (one_field.isSetExisted()){
				writable_fields.add(one_field);
			}
		}
		return writable_fields;
	}
	public static List<ClassField> loadReadableFields(IType type) throws JavaModelException{
		List<ClassField> all_fields = MiscHelper.loadJavaBeanFields(type);
		List<ClassField> readable_fields = new ArrayList<ClassField>(all_fields.size());
		for (ClassField one_field : all_fields){
			if (one_field.isGetExisted()){
				readable_fields.add(one_field);
			}
		}
		return readable_fields;
	}
	private static void updateField(List<ClassField> fields,  String rn, String type, boolean isGet) {
		
		ClassField target_field = null;
		String rawName  = StringUtils.uncapitalize(rn);
		for (ClassField f : fields){
			if (rawName.equals(f.getFieldName())){
				target_field = f;
				break;
			}
		}
		if (target_field == null){
			target_field = new ClassField(rawName);
			fields.add(target_field);
		}
		if (isGet){
			target_field.setGetExisted(true);
		}else{
			target_field.setSetExisted(true);
		}
		String tt = target_field.getType();
		if (tt == null){
			target_field.setType(type);
		}else{
			if (!tt.equals(type)){
				fields.remove(target_field);
			}
		}
	}
	public static TypeAlias getAliasByLiteral(SqlMap map, String literal){
		for (TypeAlias ta : map.getTypeAlias()){
			if (ta.getAlias().equals(literal)){
				return ta;
			}
		}
		return null;
	}
	public static void removeAndAddNewTypeAlias(List<TypeWrapper> typeWrappers,
			EList<TypeAlias> typeAlias) {
		for (Iterator<TypeWrapper> iter = typeWrappers.iterator(); iter
				.hasNext();) {
			if (iter.next().getCategory() == TypeWrapper.TYPE_ALIAS) {
				iter.remove();
			}
		}
		for (TypeAlias alias : typeAlias) {
			typeWrappers.add(new TypeWrapper(alias.getAlias(),
					TypeWrapper.TYPE_ALIAS));
		}
	}
	public static TypeWrapper getDetailWrapper(Shell shell, TypeWrapper selected_wrapper) {
		int category = selected_wrapper.getCategory();
		TypeWrapper wrapper = null;
		if (category == TypeWrapper.INTERAL
				|| category == TypeWrapper.TYPE_ALIAS) {
			wrapper = selected_wrapper;
		} else if (category == TypeWrapper.NEW_CREATE) {
			if (BatisSettingModel.getActiveModel().getBeanNeedCreate()) {
				wrapper = selected_wrapper;
			}
		} else if (category == TypeWrapper.SELECT) {
			IType selected_type = WorkspaceUtil.openType2(
					shell, BatisSettingModel.getActiveModel().getCurrentProject(),
					false);
			if (selected_type != null) {
				TypeWrapper public_wrapper = new TypeWrapper(
						selected_type
								.getFullyQualifiedName(),
						TypeWrapper.PUBLIC);
				wrapper = public_wrapper;
			}
		}
		return wrapper;
	}
	public static ResultMap getResultMapById(SqlMap sqlmap, String id) {
		for (ResultMap map : sqlmap.getResultMap()){
			if (id.equals(map.getId())){
				return map;
			}
		}
		return null;
	}
	public static String getVariableWrapper(int type, String str) {
		if (type == SqlMapParser.NVAR){
			return "#"+str+"#";
		}else if(type == SqlMapParser.QVAR){
			return "?";
		}else if(type == SqlMapParser.SVAR){
			return "$"+str+"$";
		}else{
			return null;
		}
	}
}
